using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using ValidationFramework.Reflection;
using PropertyDescriptor=ValidationFramework.Reflection.PropertyDescriptor;

namespace ValidationFramework.WPF
{

	/// <summary>
	/// A <see cref="System.Windows.Data.Binding"/> decorator that adds validation.
	/// </summary>
	[Localizability(LocalizationCategory.None, Modifiability = Modifiability.Unmodifiable, Readability = Readability.Unreadable), MarkupExtensionReturnType(typeof(object))]
	public class ValidationBinding : MarkupExtension
	{
		#region Fields

		private readonly Binding binding;

		#endregion

		static ValidationBinding()
		{
			//HACK: should do with with a style???
			DisplayStrategies = new StrategyCache<IErrorDisplayStrategy, FrameworkElement>();
			DisplayStrategies.AddStrategy(new ErrorDisplayStrategy());
			DisplayStrategies.AddStrategy(new RadioButtonErrorDisplayStrategy());
		}

        /// <inheritdoc cref="Binding" />
		public ValidationBinding()
        {
        	binding = new Binding
        	          	{
        	          		NotifyOnSourceUpdated = true,
        	          		UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
        	          		NotifyOnTargetUpdated = true
        	          	};
        }

		/// <inheritdoc cref="System.Windows.Data.Binding(string)" />
		public ValidationBinding(string path)
		{
			binding = new Binding(path)
        	          	{
        	          		NotifyOnSourceUpdated = true,
        	          		UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
        	          		NotifyOnTargetUpdated = true
        	          	};
		}


	
		
        /// <inheritdoc cref="BindingBase.ProvideValue(IServiceProvider)" />
		public override object ProvideValue(IServiceProvider serviceProvider)
		{
				var expression = binding.ProvideValue(serviceProvider) as BindingExpression;

				var target = (IProvideValueTarget) serviceProvider.GetService(typeof (IProvideValueTarget));
				var dependencyObject = (DependencyObject) target.TargetObject;
				if ((dependencyObject != null) && !DesignerProperties.GetIsInDesignMode(dependencyObject) && (LicenseManager.UsageMode != LicenseUsageMode.Designtime))
			{
				var frameworkElement = dependencyObject as FrameworkElement;
				var frameworkContentElement = dependencyObject as FrameworkContentElement;

				if (frameworkElement != null)
				{
					frameworkElement.SourceUpdated += SourceUpdated;
					frameworkElement.TargetUpdated += SourceUpdated;
				}
				else if (frameworkContentElement != null)
				{
					frameworkContentElement.SourceUpdated += SourceUpdated;
					frameworkContentElement.TargetUpdated += SourceUpdated;
				}
	}
				return expression;
		
		}

		private void SourceUpdated(object sender, DataTransferEventArgs e)
		{
			//Binding binding = BindingOperations.GetBinding((DependencyObject)sender, e.Property);
			var bindingExpression = BindingOperations.GetBindingExpressionBase((DependencyObject)sender, e.Property);
			var frameworkElement = (FrameworkElement)sender;
			string errorMessage = null;
			PropertyDescriptor propertyDescriptor;
			if (frameworkElement.DataContext != null)
			{
				object parent;
				GetPropertyPattern(frameworkElement.DataContext, binding.Path.Path, out parent, out propertyDescriptor);
				if (parent != null)
				{
					var dataErrorInfo = parent as IDataErrorInfo;

					if (dataErrorInfo == null)
					{
					    var propertyValidationManager = ValidationManagerFactory.GetPropertyValidationManager(parent, RuleSet);
					    propertyValidationManager.Context = Context;
							propertyValidationManager.Validate(propertyDescriptor.Name);
							var validationResults = propertyValidationManager.ValidatorResultsInError;
						if (validationResults.Count > 0)
						{
							errorMessage = ResultFormatter.GetConcatenatedErrorMessages(validationResults);
						}
					}
					else if (propertyDescriptor != null)
					{
						errorMessage = dataErrorInfo[propertyDescriptor.Name];
					}
					else
					{
						errorMessage = dataErrorInfo[binding.Path.Path];
					}
				}
			}
            var errorDisplayStrategy = DisplayStrategies.FindStrategy(frameworkElement);
			if (string.IsNullOrEmpty(errorMessage))
			{
				errorDisplayStrategy.ClearError(frameworkElement);
			}
			else
			{
				errorDisplayStrategy.DisplayError(bindingExpression, frameworkElement, errorMessage);
			}
		}

		private static void GetPropertyPattern(object root, string dotPath, out object parent, out PropertyDescriptor propertyDescriptor)
		{
			var typeDescriptor = TypeCache.GetType(root.GetType().TypeHandle);
			parent = root;
			propertyDescriptor = null;
			var split = dotPath.Split('.');
			for (var i = 0; i < split.Length; i++)
			{
				var pathNode = split[i];
				propertyDescriptor = typeDescriptor.GetOrCreatePropertyDescriptor(pathNode);

				if (i == split.Length - 1)
				{
					return;
				}
				parent = propertyDescriptor.GetValue(root);
				typeDescriptor = TypeCache.GetType(propertyDescriptor.RuntimeTypeHandle);
			}

			return;
		}

	

		#region Properties


		public static StrategyCache<IErrorDisplayStrategy, FrameworkElement> DisplayStrategies { get; private set; }

		/// <summary>
		/// An <see cref="object"/> to pass as the context parameter when calling <see cref="Rule.Validate"/>. The default is null.
		/// </summary>
		[DefaultValue(null)]
		public object Context
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the rule set to validate.
		/// </summary>
		[DefaultValue(null)]
		public string RuleSet
		{
			get;
			set;
		}


		#endregion

		#region BindingBase

        /// <inheritdoc cref="BindingBase.ShouldSerializeFallbackValue()" />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool ShouldSerializeFallbackValue()
		{
			return (binding.ShouldSerializeFallbackValue());
		}

        /// <inheritdoc cref="BindingBase.FallbackValue" />
		public object FallbackValue
		{
			get
			{
				return binding.FallbackValue;
			}
			set
			{
				binding.FallbackValue = value;
			}
		}

		#endregion

		#region Binding

        /// <inheritdoc cref="Binding.ShouldSerializePath()" />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool ShouldSerializePath()
		{
			return binding.ShouldSerializePath();
		}

        /// <inheritdoc cref="Binding.ShouldSerializeSource()" />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool ShouldSerializeSource()
		{
			return binding.ShouldSerializeSource();
		}

        /// <inheritdoc cref="Binding.ShouldSerializeValidationRules()" />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public bool ShouldSerializeValidationRules()
		{
			return binding.ShouldSerializeValidationRules();
		}

        /// <inheritdoc cref="Binding.AsyncState" />
		[DefaultValue((string)null)]
		public object AsyncState
		{
			get
			{
				return binding.AsyncState;
			}
			set
			{
				binding.AsyncState = value;
			}
		}

        /// <inheritdoc cref="Binding.BindsDirectlyToSource" />
		[DefaultValue(false)]
		public bool BindsDirectlyToSource
		{
			get
			{
				return binding.BindsDirectlyToSource;
			}
			set
			{
				binding.BindsDirectlyToSource = value;
			}
		}

        /// <inheritdoc cref="Binding.Converter" />
		[DefaultValue((string)null)]
		public IValueConverter Converter
		{
			get
			{
				return binding.Converter;
			}
			set
			{
				binding.Converter = value;
			}
		}

        /// <inheritdoc cref="Binding.ConverterCulture" />
		[DefaultValue((string)null), TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
		public CultureInfo ConverterCulture
		{
			get
			{
				return binding.ConverterCulture;
			}
			set
			{
				binding.ConverterCulture = value;
			}
		}

        /// <inheritdoc cref="Binding.ConverterParameter" />
		[DefaultValue((string)null)]
		public object ConverterParameter
		{
			get
			{
				return binding.ConverterParameter;
			}
			set
			{
				binding.ConverterParameter = value;
			}
		}

        /// <inheritdoc cref="Binding.ElementName" />
		[DefaultValue((string)null)]
		public string ElementName
		{
			get
			{
				return binding.ElementName;
			}
			set
			{
				binding.ElementName = value;
			}
		}

        /// <inheritdoc cref="Binding.IsAsync" />
		[DefaultValue(false)]
		public bool IsAsync
		{
			get
			{
				return binding.IsAsync;
			}
			set
			{
				binding.IsAsync = value;
			}
		}

        /// <inheritdoc cref="Binding.Mode" />
		[DefaultValue(4)]
		public BindingMode Mode
		{
			get
			{
				return binding.Mode;
			}
			set
			{
				binding.Mode = value;
			}
		}

        /// <inheritdoc cref="Binding.NotifyOnTargetUpdated" />
		[DefaultValue(true)]
		public bool NotifyOnTargetUpdated
		{
			get
			{
				return binding.NotifyOnTargetUpdated;
			}
			set
			{
				binding.NotifyOnTargetUpdated = value;
			}
		}

        /// <inheritdoc cref="Binding.NotifyOnValidationError" />
		[DefaultValue(false)]
		public bool NotifyOnValidationError
		{
			get
			{
				return binding.NotifyOnValidationError;
			}
			set
			{
				binding.NotifyOnValidationError = value;
			}
		}

        /// <inheritdoc cref="Binding.Path" />
		public PropertyPath Path
		{
			get
			{
				return binding.Path;
			}
			set
			{
				binding.Path = value;
			}
		}

        /// <inheritdoc cref="Binding.RelativeSource" />
		[DefaultValue((string)null)]
		public RelativeSource RelativeSource
		{
			get
			{
				return binding.RelativeSource;
			}
			set
			{
				binding.RelativeSource = value;
			}
		}

        /// <inheritdoc cref="Binding.Source" />
		public object Source
		{
			get
			{
				return binding.Source;
			}
			set
			{
				binding.Source = value;
			}
		}

        /// <inheritdoc cref="Binding.UpdateSourceExceptionFilter" />
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public UpdateSourceExceptionFilterCallback UpdateSourceExceptionFilter
		{
			get
			{
				return binding.UpdateSourceExceptionFilter;
			}
			set
			{
				binding.UpdateSourceExceptionFilter = value;
			}
		}

        /// <inheritdoc cref="Binding.UpdateSourceTrigger" />
		[DefaultValue(UpdateSourceTrigger.PropertyChanged)]
		public UpdateSourceTrigger UpdateSourceTrigger
		{
			get
			{
				return binding.UpdateSourceTrigger;
			}
			set
			{
				binding.UpdateSourceTrigger = value;
			}
		}

        /// <inheritdoc cref="Binding.ValidationRules" />
		public Collection<ValidationRule> ValidationRules
		{
			get
			{
				return binding.ValidationRules;
			}
		}

        /// <inheritdoc cref="Binding.XPath" />
		[DefaultValue((string)null)]
		public string XPath
		{
			get
			{
				return binding.XPath;
			}
			set
			{
				binding.XPath = value;
			}
		}

		#endregion
	}
}
